/**
 * 
 */
package truerefactor.graph;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * ClassNode - An implementation of CodeNode used to model Classes in the
 * CodeGraph
 * 
 * @author Isaac Griffith
 */
public class ClassNode extends CodeNode {

    /**
     * list of attributes contained in this class
     */
    private List<CodeNode> attributes;
    /**
     * list of methods contained in this class
     */
    private List<CodeNode> methods;
    /**
     * the package this class belongs to
     */
    private PackageNode pkgNode;
    /**
     * list of subclasses of this class
     */
    private List<ClassNode> subClasses;
    /**
     * list of parent classes of this class
     */
    private List<ClassNode> parentClasses;
    /**
     * Accessibility of this class
     */
    private Accessibility accessibility;
    /**
     * flag declaring this class to be abstract
     */
    private boolean isAbstract;
    /**
     * flag declaring this class as an interface
     */
    private boolean isInterface;
    /**
     * list of names of classes this class extends
     */
    private List<String> extds;
    /**
     * list of names of interfaces this class implements
     */
    private List<String> impls;
    /**
     * list of all classes this class is associated with
     */
    private List<ClassNode> associatedClasses;

    /**
     * Creates a new ClassNode instance with the given class name and code.
     * 
     * @param identifier
     *            name of the class
     * @param code
     *            class code
     */
    public ClassNode(String identifier, String code)
    {
        this(CodeGraph.nextUID(), identifier, code, PackageNode.getDefaultPackage(), Accessibility.Public);
    }

    /**
     * Creates a new ClassNode instance with the given unique id, class name,
     * and code
     * 
     * @param uid
     *            unique id
     * @param identifier
     *            class name
     * @param code
     *            class code
     */
    public ClassNode(long uid, String identifier, String code)
    {
        this(uid, identifier, code, PackageNode.getDefaultPackage(), Accessibility.Public);
    }

    /**
     * Creates a new ClassNode instance with the given class name, code, and
     * package it belongs to.
     * 
     * @param identifier
     *            class name
     * @param code
     *            class code
     * @param pkg
     *            package it belongs to
     */
    public ClassNode(String identifier, String code, PackageNode pkg)
    {
        this(CodeGraph.nextUID(), identifier, code, pkg, Accessibility.Public);
    }

    /**
     * Creates a new ClassNode instance with the given name, code, package it
     * belongs to, and accessibility
     * 
     * @param identifier
     *            name of the class
     * @param code
     *            code for the class
     * @param pkg
     *            package this class belongs to
     * @param access
     *            accessibility of this class
     */
    public ClassNode(long uid, String identifier, String code, PackageNode pkg, Accessibility access)
    {
        super(uid, identifier, code);

        attributes = new LinkedList<CodeNode>();
        methods = new ArrayList<CodeNode>();
        pkgNode = pkg;
        subClasses = new LinkedList<ClassNode>();
        parentClasses = new LinkedList<ClassNode>();
        accessibility = access;
        isAbstract = false;
        isInterface = false;
        impls = new LinkedList<String>();
        extds = new LinkedList<String>();
        associatedClasses = new LinkedList<ClassNode>();
    }

    /**
     * Adds the name of an interface this class is declared to implement
     * 
     * @param impl
     *            name of new interface this class is declared to implement
     */
    public void addImplements(String impl)
    {
        if (!impls.contains(impl))
            impls.add(impl);
    }

    /**
     * Adds a declaration to this class for the class with the given name
     * 
     * @param extds
     *            new name of a class that this class is declared to extend
     */
    public void addExtends(String extds)
    {
        if (!this.extds.contains(extds))
            this.extds.add(extds);
    }

    /**
     * Removes the name of an interface this class implements
     * 
     * @param impl
     *            name of interface this class no longer implements
     */
    public void removeImplemnts(String impl)
    {
        if (impls.contains(impl))
            impls.remove(impl);
    }

    /**
     * removes the class with the given name that this class is declared to
     * extend
     * 
     * @param ext
     *            name of extend declartion to remove
     */
    public void removeExtends(String ext)
    {
        if (extds.contains(ext))
            extds.remove(ext);
    }

    /**
     * @return list of all interfaces this class is declared to implement
     */
    public List<String> getImplements()
    {
        return impls;
    }

    /**
     * @return list of all classes this class is declared to extend
     */
    public List<String> getExtends()
    {
        return extds;
    }

    /**
     * Sets the flag determining if this class is to be declared an interface
     * 
     * @param isInterface
     *            new value of the interface declaration flag
     */
    public void setInterface(boolean isInterface)
    {
        this.isInterface = isInterface;
    }

    /**
     * @return true if this class is an interface
     */
    public boolean isInterface()
    {
        return isInterface;
    }

    /**
     * Sets the abstract declaration of this class
     * 
     * @param isAbstract
     *            boolean value flag for the abstract declaration of this class
     */
    public void setAbstract(boolean isAbstract)
    {
        this.isAbstract = isAbstract;
    }

    /**
     * @return true if the class was declared abstract, false otherwise
     */
    public boolean isAbstract()
    {
        return isAbstract;
    }

    /*
     * (non-Javadoc)
     * @see truerefactor.graph.CodeNode#add(truerefactor.graph.CodeNode)
     */
    @Override
    public void add(CodeNode child)
    {
        if (child instanceof MethodNode && !methods.contains(child))
        {
            methods.add(child);
            ((MethodNode) child).setParentClass(this);
        }
        else if (child instanceof AttributeNode && !attributes.contains(child))
        {
            attributes.add(child);
            ((AttributeNode) child).setParent(this);
        }
    }

    /*
     * (non-Javadoc)
     * @see truerefactor.graph.CodeNode#remove(truerefactor.graph.CodeNode)
     */
    @Override
    public void remove(CodeNode child)
    {
        if (child instanceof MethodNode && methods.contains(child))
        {
            methods.remove(child);
            ((MethodNode) child).setParentClass(null);
        }
        else if (child instanceof AttributeNode && attributes.contains(child))
        {
            attributes.remove(child);
            ((AttributeNode) child).setParent(null);
        }
    }

    /*
     * (non-Javadoc)
     * @see truerefactor.graph.CodeNode#getChild(java.lang.String)
     */
    @Override
    public CodeNode getChild(String identfier)
    {
        CodeNode retVal = null;

        List<CodeNode> children = getChildren();
        for (CodeNode node : children)
        {
            if (node.getIdentifier().equals(identifier))
            {
                retVal = node;
                break;
            }
        }

        return retVal;
    }

    /**
     * @return all attributes in this class
     */
    public List<CodeNode> getAttributes()
    {
        return attributes;
    }

    /**
     * @return all methods in this class
     */
    public List<CodeNode> getMethods()
    {
        return methods;
    }

    /**
     * @param identifier
     *            name of methods to find
     * @return list of all methods with the given name in this class
     */
    public List<MethodNode> getMethodsByName(String identifier)
    {
        List<MethodNode> retVal = new ArrayList<MethodNode>();

        for (CodeNode node : methods)
        {
            if (node instanceof MethodNode)
            {
                MethodNode mNode = (MethodNode) node;
                if (mNode.getIdentifier().equals(identifier))
                {
                    retVal.add(mNode);
                }
            }
        }

        return retVal;
    }

    /**
     * @param type
     *            return type of method to find
     * @return List of all methods contained in this class with the given return
     *         type
     */
    public List<MethodNode> getMethodsByReturnType(String type)
    {
        List<MethodNode> retVal = new ArrayList<MethodNode>();

        for (CodeNode node : methods)
        {
            if (node instanceof MethodNode)
            {
                MethodNode mNode = (MethodNode) node;
                if (mNode.getReturnType().equals(type))
                {
                    retVal.add(mNode);
                }
            }
        }

        return retVal;
    }

    /**
     * @return the package this class belongs to
     */
    public PackageNode getPackage()
    {
        return pkgNode;
    }

    /**
     * Sets the package of this class to be the one provided
     * 
     * @param pkgNode
     *            the package of this class
     */
    public void setPackage(PackageNode pNode)
    {
        this.pkgNode = pNode;
    }

    /*
     * (non-Javadoc)
     * @see truerefactor.graph.CodeNode#getChildren()
     */
    @Override
    public List<CodeNode> getChildren()
    {
        List<CodeNode> children = new ArrayList<CodeNode>();
        children.addAll(attributes);
        children.addAll(methods);
        children.addAll(subClasses);

        return children;
    }

    /**
     * Adds the provided class as a subclass of this class
     * 
     * @param node
     *            new subclass of this class
     */
    public void addSubClass(CodeNode node)
    {
        if (node instanceof ClassNode)
        {
            subClasses.add((ClassNode) node);
        }
    }

    /**
     * @return list of all subclasses of this class
     */
    public List<ClassNode> getSubClasses()
    {
        return subClasses;
    }

    /**
     * Adds the provided class as a parent of this class
     * 
     * @param node
     *            new parent class to add
     */
    public void addParentClass(CodeNode node)
    {
        if (!parentClasses.contains(node))
        {
            parentClasses.add((ClassNode) node);
        }
    }

    /**
     * @return list of all parent classes of this class
     */
    public List<ClassNode> getParentClasses()
    {
        return parentClasses;
    }

    /**
     * @return the accessibility of this class
     */
    public Accessibility getAccessibility()
    {
        return accessibility;
    }

    /**
     * Sets the accessibility of this class to the provided accessibility
     * 
     * @param accessibility
     *            new accessibility of this class
     */
    public void setAccessibility(Accessibility accessibility)
    {
        this.accessibility = accessibility;
    }

    /**
     * @param field
     *            name of attribute to find
     * @return an attribute with the given name, or null if no such attribute
     *         exists
     */
    public AttributeNode getAttribute(String field)
    {
        AttributeNode retVal = null;
        for (CodeNode node : attributes)
        {
            if (node instanceof AttributeNode)
            {
                AttributeNode aNode = (AttributeNode) node;
                if (aNode.getIdentifier().equals(field))
                {
                    retVal = aNode;
                    break;
                }
            }
        }
        return retVal;
    }

    /**
     * @param type
     *            type of attribute to find
     * @return List of attributes contained within this class with the given
     *         type
     */
    public List<AttributeNode> getAttributeByType(String type)
    {
        List<AttributeNode> retVal = new ArrayList<AttributeNode>();
        for (CodeNode node : attributes)
        {
            if (node instanceof AttributeNode)
            {
                AttributeNode aNode = (AttributeNode) node;
                if (aNode.getType().equals(type))
                {
                    retVal.add(aNode);
                }
            }
        }

        return retVal;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#clone()
     */
    @Override
    protected Object clone() throws CloneNotSupportedException
    {
        ClassNode node = new ClassNode(uid, this.identifier, this.code);
        node.setAbstract(isAbstract);
        node.setInterface(isInterface);
        node.setAccessibility(accessibility);

        for (String impl : impls)
        {
            node.addImplements(impl);
        }

        for (String extd : extds)
        {
            node.addExtends(extd);
        }

        return node;
    }

    /**
     * Adds the provided method to this class
     * 
     * @param destination
     *            method to be added
     */
    public void addMethod(MethodNode destination)
    {
        if (destination == null)
            return;

        if (!methods.contains(destination))
        {
            methods.add(destination);
            destination.setParentClass(this);
        }
    }

    /**
     * removes the provided method from this class
     * 
     * @param method
     *            method to remove
     */
    public void removeMethod(MethodNode method)
    {
        if (methods.contains(method))
            methods.remove(method);
    }

    /**
     * Adds the provided attribute to this class
     * 
     * @param attr
     *            attribute to add
     */
    public void addAttribute(AttributeNode attr)
    {
        if (!attributes.contains(attr))
        {
            attributes.add(attr);
        }
    }

    /**
     * removes the provided attribute from the list of attributes
     * 
     * @param attr
     *            attribute to be removed
     */
    public void removeAttribute(AttributeNode attr)
    {
        if (attributes.contains(attr))
            attributes.remove(attr);
    }

    /**
     * Removes the provided class from the list of parent classes
     * 
     * @param parent
     *            class to remove
     */
    public void removeParentClass(ClassNode parent)
    {
        if (parentClasses.contains(parent))
            parentClasses.remove(parent);
    }

    /**
     * Removes the provided class from the list of subclasses
     * 
     * @param sub
     *            subclass to be removed
     */
    public void removeSubClass(ClassNode sub)
    {
        if (subClasses.contains(sub))
            subClasses.remove(sub);
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString()
    {
        return identifier;
    }

    /**
     * Adds the provided class node to the list of asscoiated classes
     * 
     * @param assoc
     *            class to add
     */
    public void addAssociatedClass(ClassNode assoc)
    {
        if (!associatedClasses.contains(assoc))
            associatedClasses.add(assoc);
    }

    /**
     * Removes the provided class node from the list of associated class nodes
     * 
     * @param assoc
     *            class node to remove
     */
    public void removeAssociatedClass(ClassNode assoc)
    {
        if (associatedClasses.contains(assoc))
        {
            associatedClasses.remove(assoc);
        }
    }

    /**
     * @return list of associated classes
     */
    public List<ClassNode> getAssociatedClasses()
    {
        return associatedClasses;
    }

    /**
     * @return fully qualified class name
     */
    public String getClassName()
    {
        String retVal = identifier;

        if (identifier.length() > 1)
        {
            int lastdot = identifier.lastIndexOf('.');
            if (lastdot >= 0)
            {
                retVal = identifier.substring(lastdot + 1);
            }
        }

        return retVal;
    }
}
